package com.spring.nio.service.file.handler;

import com.spring.nio.business.file.service.dto.FileDto;
import com.spring.nio.business.file.service.param.FileQueryParam;
import com.spring.nio.common.BasicConstant;
import com.spring.nio.core.channel.context.ChannelContext;
import com.spring.nio.core.channel.context.SocketChannelContext;
import com.spring.nio.core.channel.handler.AbstractChannelHandler;
import com.spring.nio.core.event.handler.WriteEventHandler;
import com.spring.nio.core.event.listener.event.constant.ExceptionEventTypeEnum;
import com.spring.nio.core.event.listener.event.model.ExceptionEventSource;
import com.spring.nio.core.event.model.PacketData;
import com.spring.nio.core.exception.ExceptionManager;
import com.spring.nio.service.file.frame.FileMessageFrame;
import com.spring.nio.service.file.model.FileOperateEnum;
import com.spring.nio.service.file.model.FileTransportRequest;
import com.spring.nio.service.file.model.structure.FileTransportTaskDTO;
import com.spring.nio.service.file.util.FileTransportManager;
import com.spring.nio.service.protocol.response.file.FileTransportResponse;
import com.spring.nio.service.protocol.response.file.TransportResponse;
import com.spring.nio.util.LocalTime;
import com.spring.nio.util.NioUtil;
import lombok.extern.slf4j.Slf4j;
import java.io.File;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.nio.file.StandardOpenOption;
import java.sql.Timestamp;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.Objects;

/**
 * 文件下载实时流处理
 * @author spring
 * */

@Slf4j
@SuppressWarnings("all")
public class FileDownloadTransportStreamHandler extends AbstractChannelHandler {

    /**
     * @param o
     * @param channelContext
     * @throws IOException
     */
    @Override
    public void handler(Object o, ChannelContext channelContext) throws IOException {
        final SocketChannelContext socketChannelContext = ((PacketData) o).getSocketChannelContext();
        socketChannelContext.getRealList().stream().forEach(obj -> {
            FileMessageFrame fileMessageFrame = (FileMessageFrame) obj;
            // 校验DB中是否存在文件记录
            FileDto fileDto = this.checkDownloadFileIsExistDB(fileMessageFrame.getFileTransport(), socketChannelContext);
            if(!Objects.isNull(fileDto)) {
                // 校验文件系统盘符下是否存在文件
                File existFile = this.checkDownloadFileIsExistFileSystem(fileMessageFrame.getFileTransport(), socketChannelContext, fileDto);
                if(!Objects.isNull(existFile)) {
                    // 构建文件下载任务(并向客户端发送消息开始等待接收服务端文件下载流数据), 开启异步线程直接发送文件
                    FileTransportTaskDTO fileTransportTaskDto = this.createFileDownloadTaskStartup(fileMessageFrame.getFileTransport(), existFile, socketChannelContext);
                    if(!Objects.isNull(fileTransportTaskDto)) {
                        super.addFileDownloadTask(fileTransportTaskDto);
                    }
                }
            }
        });
    }

    /**
     * 校验待下载文件在DB中是否存在
     * @param fileTransportRequest
     * @param socketChannelContext
     * @return
     * */
    private FileDto checkDownloadFileIsExistDB(FileTransportRequest fileTransportRequest, SocketChannelContext socketChannelContext) {
        // 1、DB中查询待下载的文件记录
        FileQueryParam fileQueryParam = new FileQueryParam();
        fileQueryParam.setId(Long.valueOf(fileTransportRequest.getTag()));
        FileDto fileDto = fileService.getFileById(fileQueryParam);
        if(!Objects.isNull(fileDto)) {
            log.info("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] FileDownloadTransportStreamHandler | --> 用户 = [{}] 查询到待下载文件 = [{}] 记录", fileTransportRequest.getLaunchUserName(), fileTransportRequest.getFileName());
            return fileDto;
        }

        // 2、DB中为查询出文件信息，发送文件不存在消息
        log.info("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] FileDownloadTransportStreamHandler | --> 用户 = [{}] 未查询到文件 = [{}] 记录", fileTransportRequest.getLaunchUserName(), fileTransportRequest.getFileName());
        WriteEventHandler.addSendData(createDownloadFileErrorResponse("未查询到文件 [ " + fileTransportRequest.getFileName() + " ] 记录"), socketChannelContext);
        return null;
    }

    /**
     * 校验待下载文件在文件系统中是否存在
     * @param fileTransportRequest
     * @param socketChannelContext
     * @return
     * */
    private File checkDownloadFileIsExistFileSystem(FileTransportRequest fileTransportRequest, SocketChannelContext socketChannelContext, FileDto fileDto) {
        // 1、待下载文件是否真实存在
        String filePath = fileTransportRequest.getNotLogin()? (FileTransportManager.basePath + fileDto.getFilePath()) : (fileTransportRequest.getFilePath() + fileTransportRequest.getFileName());
        File executeFile = new File(filePath);
        if(executeFile.exists()){
            log.info("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] FileDownloadTransportStreamHandler | --> 用户 = [{}] 待下载物理文件 = [{}] 存在文件系统, 文件大小 = [{}]", fileTransportRequest.getLaunchUserName(), fileTransportRequest.getFileName(), fileTransportRequest.getFileSize());
            return executeFile;
        }

        // 2、数据库存在文件记录但是盘符不存在
        log.info("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] FileDownloadTransportStreamHandler | --> 用户 = [{}] 待下载物理文件 = [{}] 不存在文件系统", fileTransportRequest.getLaunchUserName(), fileTransportRequest.getFileName());
        WriteEventHandler.addSendData(createDownloadFileErrorResponse("物理文件 [ " + fileTransportRequest.getFileName() + " ] 不存在"), socketChannelContext);
        return null;
    }

    /**
     * 创建文件下载任务
     * @param fileTransport
     * @param existFile
     * @param socketChannelContext
     * @return
     * */
    private FileTransportTaskDTO createFileDownloadTaskStartup(FileTransportRequest fileTransportRequest, File existFile, SocketChannelContext socketChannelContext) {
        // 1、创建文件下载任务
        FileTransportTaskDTO fileTransportTaskDto = new FileTransportTaskDTO();
        try {
            // 可用于客户端异常情况下关闭其对应的服务端通道
            fileTransportTaskDto.setCreateDate(new Date());
            fileTransportTaskDto.setSocketChannelContext(socketChannelContext);
            fileTransportTaskDto.setFileTag(fileTransportRequest.getTag());
            fileTransportTaskDto.setFile(existFile);
            fileTransportTaskDto.setFileLength(existFile.length());
            fileTransportTaskDto.setFileChannel(FileChannel.open(existFile.toPath(), StandardOpenOption.READ));
            fileTransportTaskDto.setIsFileChannelClose(Boolean.FALSE);
            fileTransportTaskDto.setIsFileDelete(Boolean.FALSE);
            fileTransportTaskDto.setSumSendBytes(0L);
            fileTransportTaskDto.setUserName(fileTransportRequest.getLaunchUserName());
            fileTransportTaskDto.setUserId(fileTransportTaskDto.getUserId());
            fileTransportTaskDto.setTaskStatus("INIT");

        log.info("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] FileDownloadTransportStreamHandler | --> 用户 [{}] 待下载文件 [{}] 通道创建成功,下载路径 = [{}]",
                fileTransportRequest.getLaunchUserName(),
                fileTransportRequest.getFileName(),
                fileTransportTaskDto.getFile().toPath().toAbsolutePath());
            // 2、配置客户端文件下载进度循环次数,如果是已登录用户需要使用，未登录(接口调用)则可不使用
            this.configClientLoopDownload(fileTransportRequest, existFile, socketChannelContext);

        } catch (IOException e) {
            // 异常事件(ExceptionEvent) 事件源类型为异常事件源类型(EXCEPTION_EVENT_SOURCE)  事件源数据(NioReadEventExceptionSource)
            ExceptionEventSource exceptionEventSource = new ExceptionEventSource();
            exceptionEventSource.setSocketChannelContext(socketChannelContext);
            exceptionEventSource.setModule("FileDownloadTransportStreamHandler --> method [createFileDownloadTaskStartup] --> 文件下载帧创建下载任务异常");
            exceptionEventSource.setException(e);
            exceptionEventSource.setExceptionEventTypeEnum(ExceptionEventTypeEnum.FILE_TRANSPORT_HANDLER_IO_EXCEPTION);
            exceptionEventSource.setIsSend(Boolean.TRUE);
            ExceptionManager.handleException(exceptionEventSource);
            return null;
        }
        return fileTransportTaskDto;
    }

    /**
     * 配置客户端循环下载次数
     * @param fileTransportRequest
     * @param existFile
     * @param socketChannelContext
     * */
    private void configClientLoopDownload(FileTransportRequest fileTransportRequest, File existFile, SocketChannelContext socketChannelContext) {
        long fileSize = existFile.length();
        long readSize = Integer.parseInt(NioUtil.getValue(BasicConstant.DOWNLOAD_SEND_BYTE_BUFFER));
        long loopCount = 0;
        if(fileSize < readSize) {
            loopCount = 1;
        } else {
            // 总循环次数
            loopCount = fileSize / readSize;
            // 计算余数，如果余数大于0， 总循环次数 + 1
            long rest = fileSize % readSize;
            if(rest > 0) {
                loopCount = loopCount + 1;
            }
        }

        WriteEventHandler.addSendData(createDownloadFileSuccessResponse("待下载文件 [ " + existFile.getName() + " ] 任务配置完成, 即将开始进行下载", loopCount, fileSize, socketChannelContext.getRemoteAddress()), socketChannelContext);
        log.info("[ " + LocalTime.formatDate(LocalDateTime.now()) + " ] FileDownloadTransportStreamHandler | --> 用户 = [{}] 待下载文件 = [{}] 任务配置完成, 即将开始进行下载，对应客户端下载进度需要循环次数 = [{}]", fileTransportRequest.getLaunchUserName(), existFile.getName(), loopCount);
    }

    /**
     * 创建待下载文件校验成功响应Map
     * @param message
     * @param loopCount
     * @param fileSumSize
     * @param fileDownloadAddress 发送给客户端当前下载文件任务对应的服务端通道地址
     * @return
     */
    private TransportResponse createDownloadFileSuccessResponse(String message, Long loopCount, Long fileSize, String fileDownloadAddress) {
        FileTransportResponse fileTransportResponse = new FileTransportResponse();
        fileTransportResponse.setCode("200");
        fileTransportResponse.setResponseName(BasicConstant.FILE_FRAME);
        fileTransportResponse.setFrameType(FileMessageFrame.FrameType.DOWNLOAD.getBit());
        fileTransportResponse.setOperate(FileOperateEnum.DOWNLOAD_TRANSPORT_CONFIRM.getOperate());
        fileTransportResponse.setMessage(message);
        fileTransportResponse.setFileTransportAddress(fileDownloadAddress);
        fileTransportResponse.setDownloadLoopCount(loopCount);
        fileTransportResponse.setFileSize(fileSize);
        fileTransportResponse.setStatus("1");
        fileTransportResponse.setTime(Timestamp.valueOf(LocalDateTime.now()));
        return fileTransportResponse;
    }

    /**
     * 创建文件执行下载时文件校验返回错误数据集合Map
     * @param message
     */
    private TransportResponse createDownloadFileErrorResponse(String message) {
        TransportResponse transportResponse = new TransportResponse();
        transportResponse.setCode("200");
        transportResponse.setResponseName(BasicConstant.FILE_FRAME);
        transportResponse.setFrameType(FileMessageFrame.FrameType.DOWNLOAD.getBit());
        transportResponse.setOperate(FileOperateEnum.DOWNLOAD_TRANSPORT_CONFIRM_NOT_EXIST.getOperate());
        transportResponse.setMessage(message);
        transportResponse.setStatus("1");
        transportResponse.setTime(Timestamp.valueOf(LocalDateTime.now()));
        return transportResponse;
    }
}
